package littlecyberwar.ui;

import javax.swing.JPanel;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.border.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.event.*;
import java.util.*;

import littlecyberwar.ModelChangedListener;
import littlecyberwar.util.*;
import littlecyberwar.model.PlayingBoardModel;

import ModuleBuilder.model.*;

public class MiniMapPanel extends JPanel
  implements ModelChangedListener, ChangeListener, MouseMotionListener, MouseListener  {

	// same scale factor for both width & height
  public static final int _setMiniMapWidth = 240;
  ImageIcon mapIcon;
  Image mapImage;
  double width, height;
  PlayingBoardModel model;
  JViewport vp;
  
  // multiplier difference between large map & minimap
  // same scale factor for both width & height
  double scale;
  double vpX=1, vpY=1; // x,y of origin of viewport
  double vpW=1, vpH=1; // view port width and height
  Rectangle2D viewPortR;
  double halfVPX;
  double halfVPY;
  double halfEndX;
  double halfEndY;

  // offset from selection point to origin of viewPortRec
  double currOffsetX, currOffsetY;
  
  MapDrawerDelegate mapDrawer;
  
  static MiniMapPanel instance;

  public MiniMapPanel() {
	Border b = BorderFactory.createLoweredBevelBorder();
	this.setBorder(b);
	setDoubleBuffered(true);
	addMouseListener(this);
	addMouseMotionListener(this);
	mapDrawer = new MapDrawerDelegate();
	instance = this;
  }
  
  public void setImage(ImageIcon iconIn, int largeMapWidth) {
	mapIcon= iconIn;
	mapImage = mapIcon.getImage();
	width = Math.min(mapIcon.getIconWidth(), _setMiniMapWidth);
	height = mapIcon.getIconHeight();
	setLargeMapWidth(largeMapWidth);
  }
  
  public void setLargeMapWidth( int largeMapWidth ){
	scale = largeMapWidth / width;
	if ( mapIcon != null ){
		Dimension d = new Dimension(mapIcon.getIconWidth(),mapIcon.getIconHeight());
		setMaximumSize(d);
		setPreferredSize(d);
	}
	
	// setup the viewport to some default value
	Rectangle r = new Rectangle(0,0,1,1);
	setViewPortRect(r);	
	return;
  }
  
  public void setMapImage( ImageElement[][] allImg, int colIn, int rowIn, Dimension largeMapSize) {
  	// first need to figure out how much to shrink the images in order to fit in
  	// the mini map area
  	// assume a square map, so which everside the real map is larger, that side
  	// become the limiting side which we should do our calculations on
  	//
  	boolean useHorizontalConstraint=false;
  	
  	double h = largeMapSize.getHeight();
  	double w = largeMapSize.getWidth();
  	if ( w > h ) {
  		// width is larger
  		scale = w / _setMiniMapWidth;
		width = _setMiniMapWidth;
		height = h / scale;
		useHorizontalConstraint=true;	
  	} else {
  		scale = h / _setMiniMapWidth;
  		height = _setMiniMapWidth;
  		width = w / scale;
  	}
	setupMiniMapTiles(allImg, colIn, rowIn, scale, useHorizontalConstraint);
	mapDrawer.setMapImage(allImg, colIn, rowIn, false);
	// next setup the size of the map
	Dimension d = new Dimension((int)width, (int)height);
	// Debug.out("MIniMap size=" + d.toString());
	this.setPreferredSize(d);
	this.setMaximumSize(d);
	this.setMinimumSize(d);
	validate();
	repaint();
	return;
  }

	// loop through every imageicon, and then shrink the large image into the small image
	//
  private void setupMiniMapTiles( ImageElement[][] allImg, 
  									int colIn, 
  									int rowIn, 
  									double scaleIn, 
  									boolean useHorizontalConstraint ) {
	for (int i=0; i < colIn; i++ ) {
		for (int j=0; j< rowIn; j++) {
			ImageElement el = allImg[i][j];
			ImageIcon largeIcon = el.getFullImageIcon();
			ImageIcon smallIcon=null;
			if (useHorizontalConstraint) {			
				smallIcon = new ImageIcon( largeIcon.getImage().
											getScaledInstance(
												_setMiniMapWidth/colIn, 
												-1,
												Image.SCALE_SMOOTH ));
			} else {
				smallIcon = new ImageIcon( largeIcon.getImage().
											getScaledInstance(
												-1,
												_setMiniMapWidth/rowIn, 
												Image.SCALE_SMOOTH ));
			}
			el.setSmallIcon(smallIcon);
			// Debug.out("SmallIcon="+smallIcon.toString());
		}
	}
	return;
  }
  
  // deprecated
//  public MiniMapPanel(ImageIcon iconIn, int largeMapWidth) {
//    mapIcon= iconIn;
//    mapImage = mapIcon.getImage();
//    width = Math.min(mapIcon.getIconWidth(), _setMiniMapWidth);
//    height = mapIcon.getIconHeight();
//    scale = largeMapWidth / width;
//    Debug.out("w=" + width + ", h=" + height + ",big map width=" + largeMapWidth+ ", scale=" + scale);
//    Border b = BorderFactory.createLoweredBevelBorder();
//    this.setBorder(b);
//    Dimension d = new Dimension(mapIcon.getIconWidth(),mapIcon.getIconHeight());
//    setMaximumSize(d);
//    setPreferredSize(d);
//
//    setDoubleBuffered(true);
//    setBackground(Color.black);
//
//    addMouseListener(this);
//    addMouseMotionListener(this);
//  }

  // input = origin (top left podouble) of
  // the large map view port
  public void setViewPortOrigin(double x, double y) {
    vpX = x / scale;
    vpY = y / scale;
    viewPortR = new Rectangle2D.Double(vpX, vpY, vpW, vpH);
  }

  public void setViewPortSize(double w, double h) {
    vpW = w / scale;
    vpH = h / scale;
    viewPortR = new Rectangle2D.Double(vpX, vpY, vpW, vpH);
  }

  public void setViewPort( JViewport v ) {
    vp = v;
    viewPortChangedCore(vp);
    vp.addChangeListener(this);
  }

  public void setViewPortRect( Rectangle r ) {
    double x= r.getX();
    double y = r.getY();
    double w = r.getWidth();
    double h = r.getHeight();
    setViewPortOrigin(x,y);
    setViewPortSize(w,h);
    repaint();
  }

  public void setModel(PlayingBoardModel m) {
    model = m;
    model.subscribeModelChange(this);
  }

  public void paintComponent( Graphics g ) {
    // for now just paint a stupid rectangle
    super.paintComponent(g);
    Graphics2D g2 = (Graphics2D)g;
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);

    // first paint the background map
    //
	mapDrawer.drawMap(g2);
	
    ArrayList v = model.getAllElements();
    for ( int i=0; i< v.size(); i++ ) {
      Element e = (Element)v.get(i);
      Point2D lfPold = e.getFinalLeftFrontPoint();
      Point2D lfP = ScalingDelegate.getOriginalFromScale2D(lfPold);
      
      // paint a solid rectangle where the center of the element is
      //!! could improve performance by not creating new rectangle?
      Rectangle2D r = new Rectangle2D.Double((lfP.getX()/scale), (lfP.getY()/scale), 4, 4);
      g2.setColor(Color.red);
      g2.fill(r);
    }
    // draw the outline of the viewPort
    //!! use another definable color for the view port
    if ( viewPortR != null ) {
      g2.setColor(Color.red);
      g2.draw( viewPortR );
    }
  }

  /**
   * If the model changed (i.e. more units added or subtracted)
   * The entire board needs to be repainted
   */
  public void modelChanged() {
    repaint();
  }

  public void modelChanged( Rectangle r ) {
    System.out.println("TBD");
  }

  /**
  * from the ChangeListener interface
  * this is called when the main map viewport has changed to somewhere else
  * in this case only redraw where the view port has moved
  */
  public void stateChanged( ChangeEvent e ) {
    Object obj = e.getSource();
    if ( obj.equals(vp)) {
      Rectangle r = viewPortR.getBounds();
      viewPortChangedCore(vp);
      r.add(viewPortR.getBounds());
      r.grow(2,2);
      repaint(r);
    }
  }

  private void viewPortChangedCore( JViewport vp ) {
    Rectangle r = vp.getViewRect();
    setViewPortRect(r);
    halfVPX = viewPortR.getWidth() /2;
    halfVPY = viewPortR.getHeight() /2;

    // calculate 1/2 of end distance as well
    //
    halfEndX = width - halfVPX;
    halfEndY = height - halfVPY;
  }

  // if mouse is pressed, then check if this is inside the current
  // rec.
  // if it's inside the viewPortR, then do not change the view, but
  // instead setup what could be the start of the viewPort drag operation
  //
  public void mousePressed(MouseEvent e) {
    // see which element is selected (if any)
    Point p = e.getPoint();
    double x = p.getX();
    double y = p.getY();
    if ( !viewPortR.contains((Point2D)p ) ) {
      // it's outside the vp, center vp rectangle on this point
      centerViewPort(p);
    }
    // could be the start of a drag, record the offset to current
    // upper left point of the viewport
    //
    double viewX = viewPortR.getX();
    double viewY = viewPortR.getY();
    currOffsetX = x - viewX;
    currOffsetY = y - viewY;
    return;
  }

  public void mouseReleased(MouseEvent e) {
    currOffsetX = 0;
    currOffsetY = 0;
  }

  public void mouseEntered(MouseEvent e) {
  }

  public void mouseExited(MouseEvent e) {
  }

  public void mouseClicked(MouseEvent e) {
  }

  public void mouseMoved(MouseEvent e) {
  }

  public void mouseDragged(MouseEvent e) {
    Point p = e.getPoint();
    double x = p.getX() - currOffsetX;
    double y = p.getY() - currOffsetY;
    if ( x < 0 ) {
      x = 0;
    } else if ( x > (width - viewPortR.getWidth()) ) {
      x = width - viewPortR.getWidth();
    }
    if ( y < 0 ) {
      y = 0;
    } else if ( y > (height-viewPortR.getHeight()) ) {
      y = height-viewPortR.getHeight();
    }
    vp.setViewPosition(new Point((int)(x*scale),(int)(y*scale)));
  }

  private void centerViewPort( Point2D p ) {
    // check if the center point is near any of the four edges,
    // if so, chnage the distance of the center
    //
    double x = p.getX();
    double y = p.getY();
    if ( x < halfVPX ) {
      x = halfVPX;
    } else if ( x > halfEndX ) {
      x = halfEndX;
    }
    if ( y < halfVPY ) {
      y = halfVPY;
    } else if ( y > halfEndY ) {
      y = halfEndY;
    }
    vp.setViewPosition(new Point((int)((x-halfVPX)*scale),(int)((y-halfVPY)*scale)));
  }

/**
 * @return Returns the instance.
 */
public static MiniMapPanel getInstance() {
	return instance;
}
}